package com.naiterui.ehp.bp.utils.net;

import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.naiterui.ehp.bp.support.exception.BaseExceptionCodes;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.exception.ResponseVO;
import com.naiterui.ehp.bp.support.security.HeaderConstant;
import com.naiterui.ehp.bp.support.utils.RestTemplateUtil;
import com.naiterui.ehp.bp.utils.context.SpringContextHolder;
import com.naiterui.ehp.bp.utils.thread.ThreadPoolUtil;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * 远程请求HTTP工具类，此工具类依赖ioc容器，支持LB
 *
 * @author wangzehao
 * @date 2018/10/10 10:10 AM
 * @since 1.0.0
 */
@SuppressWarnings({"unused"})
public class RestHttpUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(RestHttpUtil.class);
    /**
     * 请求地址
     */
    private String url;
    /**
     * 请求方式
     */
    private HttpMethod method;
    /**
     * header
     */
    private Map<String, String> header;
    /**
     * body
     */
    private String body;
    /**
     * GET: 默认fasle, 请求参数是否需要编码(中文需要编码)
     */
    private boolean needEncode = false;
    /**
     * 请求参数
     */
    private Map<String, ?> paramMap;
    /**
     * 链接超时时间
     */
    private Integer connectTimeout;
    /**
     * 读取超时时间
     */
    private Integer readTimeout;

    /**
     * 是否打印请求日志
     */
    private Boolean logSkipBigParam = true;

    private RestHttpUtil() {
    }

    public static RestHttpUtil get() {
        return new RestHttpUtil().method(HttpMethod.GET);
    }

    public static RestHttpUtil post() {
        return new RestHttpUtil().method(HttpMethod.POST);
    }

    public static RestHttpUtil put() {
        return new RestHttpUtil().method(HttpMethod.PUT);
    }

    public static RestHttpUtil delete() {
        return new RestHttpUtil().method(HttpMethod.DELETE);
    }

    public RestHttpUtil method(HttpMethod method) {
        this.method = method;
        return this;
    }

    public RestHttpUtil url(String url) {
        this.url = url;
        return this;
    }

    public RestHttpUtil header(Map<String, String> header) {
        this.header = header;
        return this;
    }

    public RestHttpUtil body(String body) {
        this.body = body;
        return this;
    }

    public RestHttpUtil paramMap(Map<String, ?> paramMap) {
        this.paramMap = paramMap;
        return this;
    }

    public RestHttpUtil needEncode(boolean needEncode) {
        this.needEncode = needEncode;
        return this;
    }

    public RestHttpUtil connectTimeout(Integer connectTimeout) {
        this.connectTimeout = connectTimeout;
        return this;
    }

    public RestHttpUtil readTimeout(Integer readTimeout) {
        this.readTimeout = readTimeout;
        return this;
    }

    public RestHttpUtil logSkipBigParam(boolean skip) {
        this.logSkipBigParam = skip;
        return this;
    }

    /**
     * 获取RestTemplate
     *
     * @return
     */
    private RestTemplate getRestTemplate() {
        RestTemplate restTemplate = SpringContextHolder.getBean(RestTemplate.class);
        // 处理401异常
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 401) {
                    super.handleError(response);
                }
            }
        });
        Integer customConnectTimeout = Optional.ofNullable(this.connectTimeout).orElse(RestTemplateUtil.DEFAULT_CONNECT_TIMEOUT);
        Integer customReadTimeout = Optional.ofNullable(this.readTimeout).orElse(RestTemplateUtil.DEFAULT_READ_TIMEOUT);
        if (RestTemplateUtil.DEFAULT_CONNECT_TIMEOUT.equals(customConnectTimeout) && RestTemplateUtil.DEFAULT_READ_TIMEOUT.equals(
            customReadTimeout)) {
            return restTemplate;
        }
        LOGGER.info("自定义请求超时时间 url:{} connectTimeout:{} readTimeout:{}", this.url, this.connectTimeout, this.readTimeout);
        RestTemplate customRestTemplate = RestTemplateUtil.create(this.connectTimeout, this.readTimeout);
        customRestTemplate.setInterceptors(restTemplate.getInterceptors());
        return customRestTemplate;
    }

    /**
     * 执行请求
     *
     * @param responseTypeReference 返回类型
     * @param <T>
     * @return
     */
    public <T> ResponseEntity<T> execute(ParameterizedTypeReference<T> responseTypeReference) {

        Map<String, Object> printParam = Maps.newHashMap();
        if (CollectionUtil.isNotEmpty(paramMap)) {
            paramMap.forEach((k, v) -> {
                if (v instanceof String && ((String) v).length() > 255) {
                    printParam.put(k, "[DataTooBig]");
                } else {
                    printParam.put(k, v);
                }
            });
        }

        LOGGER.info("请求远程地址 请求参数：url = {}, method = {}, header = {}, body = {}, paramMap = {}", this.url, this.method, this.header, this.body,
            printParam);
        if (HttpMethod.GET == this.method && StringUtils.isNotEmpty(this.body)) {
            throw new IllegalStateException(String.format("HttpMethod is GET,Body should be null !!! url:%s", this.url));
        }
        RestTemplate restTemplate = this.getRestTemplate();
        Assert.notNull(restTemplate, "restTemplate 还未初始化");
        Assert.notNull(this.url, String.format("restTemplate should be null,url:%s", this.url));
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.add(HeaderConstant.DATA_NO_WRAP, "1");
        Optional.ofNullable(this.header).orElse(Maps.newHashMap()).forEach(requestHeaders::add);
        long startTime = System.currentTimeMillis();
        try {
            if (StringUtils.isNotEmpty(this.body)) {
                requestHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
                HttpEntity<String> requestEntity = new HttpEntity<>(this.body, requestHeaders);
                return restTemplate.exchange(this.url, this.method, requestEntity, responseTypeReference);
            }
            requestHeaders.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
            MultiValueMap<String, Object> requestMap = new LinkedMultiValueMap<>();
            Optional.ofNullable(this.paramMap).orElse(Maps.newHashMap()).forEach((key, value) -> {
                if (value != null) {
                    if (value instanceof Object[]) {
                        Object[] tempValues = (Object[]) value;
                        requestMap.put(key, Arrays.stream(tempValues)
                            .filter(Objects::nonNull)
                            .map(Object::toString)
                            .collect(Collectors.toCollection(Lists::newArrayList)));
                    } else {
                        requestMap.add(key, value);
                    }
                }
            });
            //如果是get 请求,那么拼接URL参数
            if (HttpMethod.GET == this.method) {
                UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(this.url);
                requestMap.forEach((key, objects) -> {
                        uriComponentsBuilder.queryParam(key, objects.toArray());
                    }
                );
                URI uri = uriComponentsBuilder.build(!this.needEncode).toUri();
                HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(requestMap, requestHeaders);
                return restTemplate.exchange(uri, this.method, requestEntity, responseTypeReference);
            }
            HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(requestMap, requestHeaders);
            //这里不使用url传递，默认会编码
            URI uri = UriComponentsBuilder.fromHttpUrl(this.url).build(true).toUri();
            return restTemplate.exchange(uri, this.method, requestEntity, responseTypeReference);
        } catch (Exception e) {
            if (e.getCause() instanceof SocketTimeoutException) {
                LOGGER.error("{} 请求超时,请求时间:{} ms", this.url, System.currentTimeMillis() - startTime);
                return new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT);
            }
            LOGGER.error("{} 请求失败", this.url, e);
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        } finally {
            LOGGER.info("{} 请求时间:{} ms", this.url, System.currentTimeMillis() - startTime);
        }
    }

    public <T> T executeData(ParameterizedTypeReference<ResponseVO<T>> responseTypeReference) throws BusinessException {
        ResponseEntity<ResponseVO<T>> responseEntity = this.execute(responseTypeReference);
        if (!HttpStatus.Series.valueOf(responseEntity.getStatusCodeValue()).equals(HttpStatus.Series.SUCCESSFUL)) {
            throw new BusinessException(BaseExceptionCodes.HTTP_ERROR);
        }
        ResponseVO<T> responseVO = responseEntity.getBody();
        int code = responseVO.getCode();
        if (code != BaseExceptionCodes.SUCCESS) {
            throw new BusinessException(code, responseVO.getMsg());
        }
        return responseVO.getData();
    }

    public void executeData() throws BusinessException {
        ResponseEntity<ResponseVO<Object>> responseEntity = this.execute(new ParameterizedTypeReference<ResponseVO<Object>>() {
        });
        if (!HttpStatus.Series.valueOf(responseEntity.getStatusCodeValue()).equals(HttpStatus.Series.SUCCESSFUL)) {
            throw new BusinessException(BaseExceptionCodes.HTTP_ERROR, responseEntity.toString());
        }
        ResponseVO<Object> responseVO = responseEntity.getBody();
        int code = responseVO.getCode();
        if (code != BaseExceptionCodes.SUCCESS) {
            throw new BusinessException(code, responseVO.getMsg());
        }
    }

    public void asyncExecuteData() {
        ThreadPoolUtil.execute(() -> {
            ResponseEntity<ResponseVO<Object>> responseEntity = RestHttpUtil.this.execute(new ParameterizedTypeReference<ResponseVO<Object>>() {
            });
            if (!HttpStatus.Series.valueOf(responseEntity.getStatusCodeValue()).equals(HttpStatus.Series.SUCCESSFUL)) {
                LOGGER.error("请求失败 url:{}", this.url);
            }
        });

    }

}
